Bruce Eckel - 详解函数式编程(卷一)
Bruce Eckel
读完需要
20分钟速读仅需 5 分钟
布鲁斯 • 埃克尔(Bruce Eckel),C++ 标准委员会的创始成员之一,知名技术顾问,专注于编程语言和软件系统设计方面的研究,常活跃于世界各大顶级技术研讨会。
他自 1986 年以来,累计出版 Thinking in C++、Thinking in Java、On Java 等十余部经典计算机著作,曾多次荣获 Jolt 最佳图书奖(被誉为“软件业界的奥斯卡”),其代表作 Thinking in Java 被译为中文、日文、俄文、意大利文、波兰文、韩文等十几种语言,在世界范围内产生了广泛影响。
函数式编程语言处理代码片段就像处理数据一样简单。尽管 Java 并非函数式语言,但是 Java 8 的 lambda 表达式和方法引用允许我们以函数式风格编程。
面向对象编程抽象数据,而函数式编程抽象行为。
1
旧方式与新方式
// functional/Strategize.java
interface Strategy {
String approach(String msg);
}
class Soft implements Strategy {
@Override
public String approach(String msg) {
return msg.toLowerCase() + "?";
}
}
class Unrelated {
static String twice(String msg) {
return msg + " " + msg;
}
}
public class Strategize {
Strategy strategy;
String msg;
Strategize(String msg) {
strategy = new Soft(); // [1]
this.msg = msg;
}
void communicate() {
System.out.println(strategy.approach(msg));
}
void changeStrategy(Strategy strategy) {
this.strategy = strategy;
}
public static void main(String[] args) {
Strategy[] strategies = {
new Strategy() { // [2]
public String approach(String msg) {
return msg.toUpperCase() + "!";
}
},
msg -> msg.substring(0, 5), // [3]
Unrelated::twice // [4]
};
Strategize s = new Strategize("Hello there");
s.communicate();
for(Strategy newStrategy : strategies) {
s.changeStrategy(newStrategy); // [5]
s.communicate(); // [6]
}
}
}
/* 输出:
hello there?
HELLO THERE!
Hello
Hello there Hello there
*/
[2] 更简洁、自然的方式是创建一个匿名内部类。这样仍然会存在一定数量的重复代码,而且我们总是要花点功夫才能明白这里是在使用匿名内部类。
[3] 这是Java 8的lambda表达式,突出的特点是用箭头->将参数和函数体分隔开来。箭头右边是从lambda返回的表达式。这和类定义以及匿名内部类实现了同样的效果,但是代码要少得多。
[4] 这是Java 8的方法引用,突出的特点是::。::的左边是类名或对象名,右边是方法名,但是没有参数列表。
[5] 在使用了默认的Soft策略之后,我们遍历数组中的所有策略,并使用changeStrategy()方法将每个策略放入s中。
[6] 现在,每次调用communicate()都会产生不同的行为,这取决于此时所使用的策略“代码对象”。我们传递了行为,而不只是传递数据。[2]
2
lambda 表达式
// functional/LambdaExpressions.java
interface Description {
String brief();
}
interface Body {
String detailed(String head);
}
interface Multi {
String twoArg(String head, Double d);
}
public class LambdaExpressions {
static Body bod = h -> h + " No Parens!"; // [1]
static Body bod2 = (h) -> h + " More details"; // [2]
static Description desc = () -> "Short info"; // [3]
static Multi mult = (h, n) -> h + n; // [4]
static Description moreLines = () -> { // [5]
System.out.println("moreLines()");
return "from moreLines()";
};
public static void main(String[] args) {
System.out.println(bod.detailed("Oh!"));
System.out.println(bod2.detailed("Hi!"));
System.out.println(desc.brief());
System.out.println(mult.twoArg("Pi! ", 3.14159));
System.out.println(moreLines.brief());
}
}
/* 输出:
Oh! No Parens!
Hi! More details
Short info
Pi! 3.14159
moreLines()
from moreLines()
*/
[2] 通常情况是用括号将参数包裹起来。为了一致性,在单个参数时也可以使用括号,尽管这并不常见。
[3] 在没有参数的情况下,必须使用括号来指示空的参数列表。
[4] 在有多个参数的情况下,将它们放在使用括号包裹起来的参数列表内。
递归
// functional/IntCall.java
interface IntCall {
int call(int arg);
}
// functional/RecursiveFactorial.java
public class RecursiveFactorial {
static IntCall fact;
public static void main(String[] args) {
fact = n -> n == 0 ? 1 : n * fact.call(n - 1);
for(int i = 0; i <= 10; i++)
System.out.println(fact.call(i));
}
}
/* 输出:
1
1
2
6
24
120
720
5040
40320
362880
3628800
*/
// functional/RecursiveFibonacci.java
public class RecursiveFibonacci {
IntCall fib;
RecursiveFibonacci() {
fib = n -> n == 0 ? 0 :
n == 1 ? 1 :
fib.call(n - 1) + fib.call(n - 2);
}
int fibonacci(int n) { return fib.call(n); }
public static void main(String[] args) {
RecursiveFibonacci rf = new RecursiveFibonacci();
for(int i = 0; i <= 10; i++)
System.out.println(rf.fibonacci(i));
}
}
/* 输出:
0
1
1
2
3
5
8
13
21
34
55
*/
3
方法引用
// functional/MethodReferences.java
interface Callable { // [1]
void call(String s);
}
class Describe {
void show(String msg) { // [2]
System.out.println(msg);
}
}
public class MethodReferences {
static void hello(String name) { // [3]
System.out.println("Hello, " + name);
}
static class Description {
String about;
Description(String desc) { about = desc; }
void help(String msg) { // [4]
System.out.println(about + " " + msg);
}
}
static class Helper {
static void assist(String msg) { // [5]
System.out.println(msg);
}
}
public static void main(String[] args) {
Describe d = new Describe();
Callable c = d::show; // [6]
c.call("call()"); // [7]
c = MethodReferences::hello; // [8]
c.call("Bob");
c = new Description("valuable")::help; // [9]
c.call("information");
c = Helper::assist; // [10]
c.call("Help!");
}
}
/* 输出:
call()
Hello, Bob
valuable information
Help!
*/
[2] show()的签名(参数类型和返回类型)和Callable中call()的签名一致。
[3] hello()的签名也和call()一致。
[4] help()是静态内部类中的一个非静态方法。
[5] assist()是静态内部类中的一个静态方法。
[6] 我们将Describe对象的一个方法引用赋值给了一个Callable,Callable中没有show()方法,只有一个call()方法。然而,Java似乎对这种看似奇怪的赋值并没有意见,因为这个方法引用的签名和Callable中的call()方法一致。
[7] 现在可以通过调用call()来调用show(),因为Java将call()映射到了show()上。
[8] 这是一个静态方法引用。
[9] 这是[6]的另一个版本:对某个活跃对象上的方法的方法引用,有时叫作“绑定方法引用”(bound method reference)。
[10] 最后,获得静态内部类中的静态方法的方法引用,看起来就像在[8]处的外部类版本。
3.1
Runnable
// functional/RunnableMethodReference.java
// 使用Runnable接口的方法引用
class Go {
static void go() {
System.out.println("Go::go()");
}
}
public class RunnableMethodReference {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("Anonymous");
}
}).start();
new Thread(
() -> System.out.println("lambda")
).start();
new Thread(Go::go).start();
}
}
/* 输出:
Anonymous
lambda
Go::go()
*/
3.2
未绑定方法引用
// functional/UnboundMethodReference.java
// 未绑定对象的方法引用
class X {
String f() { return "X::f()"; }
}
interface MakeString {
String make();
}
interface TransformX {
String transform(X x);
}
public class UnboundMethodReference {
public static void main(String[] args) {
// MakeString ms = X::f; // [1]
TransformX sp = X::f;
X x = new X();
System.out.println(sp.transform(x)); // [2]
System.out.println(x.f()); // 效果相同
}
}
/* 输出:
X::f()
X::f()
*/
// functional/MultiUnbound.java
// 有多个参数的未绑定方法
class This {
void two(int i, double d) {}
void three(int i, double d, String s) {}
void four(int i, double d, String s, char c) {}
}
interface TwoArgs {
void call2(This athis, int i, double d);
}
interface ThreeArgs {
void call3(This athis, int i, double d, String s);
}
interface FourArgs {
void call4(
This athis, int i, double d, String s, char c);
}
public class MultiUnbound {
public static void main(String[] args) {
TwoArgs twoargs = This::two;
ThreeArgs threeargs = This::three;
FourArgs fourargs = This::four;
This athis = new This();
twoargs.call2(athis, 11, 3.14);
threeargs.call3(athis, 11, 3.14, "Three");
fourargs.call4(athis, 11, 3.14, "Four", 'Z');
}
}
3.3
构造器方法引用
// functional/CtorReference.java
class Dog {
String name;
int age = -1; // For "unknown"
Dog() { name = "stray"; }
Dog(String nm) { name = nm; }
Dog(String nm, int yrs) { name = nm; age = yrs; }
}
interface MakeNoArgs {
Dog make();
}
interface Make1Arg {
Dog make(String nm);
}
interface Make2Args {
Dog make(String nm, int age);
}
public class CtorReference {
public static void main(String[] args) {
MakeNoArgs mna = Dog::new; // [1]
Make1Arg m1a = Dog::new; // [2]
Make2Args m2a = Dog::new; // [3]
Dog dn = mna.make();
Dog d1 = m1a.make("Comet");
Dog d2 = m2a.make("Ralph", 4);
}
}
4
函数式接口
(x, y) -> x + y
System.out::println
// functional/FunctionalAnnotation.java
@FunctionalInterface
interface Functional {
String goodbye(String arg);
}
interface FunctionalNoAnn {
String goodbye(String arg);
}
/*
@FunctionalInterface
interface NotFunctional {
String goodbye(String arg);
String hello(String arg);
}
产生报错信息:
NotFunctional is not a functional interface
multiple non-overriding abstract methods
found in interface NotFunctional
*/
public class FunctionalAnnotation {
public String goodbye(String arg) {
return "Goodbye, " + arg;
}
public static void main(String[] args) {
FunctionalAnnotation fa =
new FunctionalAnnotation();
Functional f = fa::goodbye;
FunctionalNoAnn fna = fa::goodbye;
// Functional fac = fa; // 不兼容
Functional fl = a -> "Goodbye, " + a;
FunctionalNoAnn fnal = a -> "Goodbye, " + a;
}
}
// functional/FunctionVariants.java
import java.util.function.*;
class Foo {}
class Bar {
Foo f;
Bar(Foo f) { this.f = f; }
}
class IBaz {
int i;
IBaz(int i) {
this.i = i;
}
}
class LBaz {
long l;
LBaz(long l) {
this.l = l;
}
}
class DBaz {
double d;
DBaz(double d) {
this.d = d;
}
}
public class FunctionVariants {
static Functionf1 = f -> new Bar(f);
static IntFunctionf2 = i -> new IBaz(i);
static LongFunctionf3 = l -> new LBaz(l);
static DoubleFunctionf4 = d -> new DBaz(d);
static ToIntFunctionf5 = ib -> ib.i;
static ToLongFunctionf6 = lb -> lb.l;
static ToDoubleFunctionf7 = db -> db.d;
static IntToLongFunction f8 = i -> i;
static IntToDoubleFunction f9 = i -> i;
static LongToIntFunction f10 = l -> (int)l;
static LongToDoubleFunction f11 = l -> l;
static DoubleToIntFunction f12 = d -> (int)d;
static DoubleToLongFunction f13 = d -> (long)d;
public static void main(String[] args) {
Bar b = f1.apply(new Foo());
IBaz ib = f2.apply(11);
LBaz lb = f3.apply(11);
DBaz db = f4.apply(11);
int i = f5.applyAsInt(ib);
long l = f6.applyAsLong(lb);
double d = f7.applyAsDouble(db);
l = f8.applyAsLong(12);
d = f9.applyAsDouble(12);
i = f10.applyAsInt(12);
d = f11.applyAsDouble(12);
i = f12.applyAsInt(13.0);
l = f13.applyAsLong(13.0);
}
}
我们尝试让这些lambda表达式生成能匹配签名的最简单的代码。在某些情况下,必须执行类型转换,否则编译器会报截断错误。
// functional/MethodConversion.java
import java.util.function.*;
class In1 {}
class In2 {}
public class MethodConversion {
static void accept(In1 i1, In2 i2) {
System.out.println("accept()");
}
static void someOtherName(In1 i1, In2 i2) {
System.out.println("someOtherName()");
}
public static void main(String[] args) {
BiConsumerbic;
bic = MethodConversion::accept;
bic.accept(new In1(), new In2());
bic = MethodConversion::someOtherName;
// bic.someOtherName(new In1(), new In2()); // 不行
bic.accept(new In1(), new In2());
}
}
/* 输出:
accept()
someOtherName()
*/
查阅BiConsumer的文档,会看到它的函数式方法是accept()。确实,如果将我们的方法命名为accept(),它可以用作方法引用。但如果给它起个完全不同的名字,比如someOtherName(),只要参数类型和返回类型与BiConsumer的accept()相同,也是没问题的。
// functional/ClassFunctionals.java
import java.util.*;
import java.util.function.*;
class AA {}
class BB {}
class CC {}
public class ClassFunctionals {
static AA f1() { return new AA(); }
static int f2(AA aa1, AA aa2) { return 1; }
static void f3(AA aa) {}
static void f4(AA aa, BB bb) {}
static CC f5(AA aa) { return new CC(); }
static CC f6(AA aa, BB bb) { return new CC(); }
static boolean f7(AA aa) { return true; }
static boolean f8(AA aa, BB bb) { return true; }
static AA f9(AA aa) { return new AA(); }
static AA f10(AA aa1, AA aa2) { return new AA(); }
public static void main(String[] args) {
Suppliers = ClassFunctionals::f1;
s.get();
Comparatorc = ClassFunctionals::f2;
c.compare(new AA(), new AA());
Consumercons = ClassFunctionals::f3;
cons.accept(new AA());
BiConsumerbicons = ClassFunctionals::f4;
bicons.accept(new AA(), new BB());
Functionf = ClassFunctionals::f5;
CC cc = f.apply(new AA());
BiFunctionbif = ClassFunctionals::f6;
cc = bif.apply(new AA(), new BB());
Predicatep = ClassFunctionals::f7;
boolean result = p.test(new AA());
BiPredicatebip = ClassFunctionals::f8;
result = bip.test(new AA(), new BB());
UnaryOperatoruo = ClassFunctionals::f9;
AA aa = uo.apply(new AA());
BinaryOperatorbo = ClassFunctionals::f10;
aa = bo.apply(new AA(), new AA());
}
}
注意,每个方法的名称可以是任意的(比如f1()、f2()等),但是正如我们刚才看到的,一旦方法引用被赋值给某个函数式接口,就可以调用与这个接口关联的函数式方法了。在这个示例中,这些方法是get()、compare()、accept()、apply()和test()。
4.1
带有更多参数的函数式接口
// functional/TriFunction.java
@FunctionalInterface
public interface TriFunction{
R apply(T t, U u, V v);
}
,>
// functional/TriFunctionTest.java
public class TriFunctionTest {
static int f(int i, long l, double d) { return 99; }
public static void main(String[] args) {
TriFunctiontf =
TriFunctionTest::f;
tf = (i, l, d) -> 12;
}
}
方法引用和lambda表达式我们都做了测试。
4.2
解决缺乏基本类型函数式接口的问题
// functional/BiConsumerPermutations.java
import java.util.function.*;
public class BiConsumerPermutations {
static BiConsumerbicid = (i, d) ->
System.out.format("%d, %f%n", i, d);
static BiConsumerbicdi = (d, i) ->
System.out.format("%d, %f%n", i, d);
static BiConsumerbicil = (i, l) ->
System.out.format("%d, %d%n", i, l);
public static void main(String[] args) {
bicid.accept(47, 11.34);
bicdi.accept(22.45, 92);
bicil.accept(1, 11L);
}
}
/* 输出:
47, 11.340000
92, 22.450000
1, 11
*/
// functional/FunctionWithWrapped.java
import java.util.function.*;
public class FunctionWithWrapped {
public static void main(String[] args) {
Functionfid = i -> (double)i;
IntToDoubleFunction fid2 = i -> i;
}
}
@FunctionalInterface
public interface IntToDoubleFunction {
double applyAsDouble(int value);
}
本书特色
l 查漏宝典:涵盖Java关键特性的设计原理和应用方法
l 避坑指南:以产业实践的得失为鉴,指明Java开发者不可不知的设计陷阱
l 经典普适:值得不同层次的Java开发者反复研读
l 专家领读:4位一线业务专家、知名作译者帮你拆解书中难点,总结Java开发精要
值得一提的是,为了帮助新手加深理解,出版方邀请了4位从业10年以上知名作译者(DDD 专家张逸、服务端专家梁桂钊、软件系统架构专家王前明、译者陈德伟)为本书录制【精讲视频】和【导读指南】,该视频已在B站和图灵社区发布,感兴趣的朋友可以去看看。
读者福利 :
618特别活动,基础卷限时5折,800多页软精装到手价64.9!!
618限时5折专享
想购买全套装书的读者,1400多页软精装到手价160
限量套装,售完为止
往期推荐
如葑:阿里云原生网关Envoy Gateway实践
如何用研发效能搞垮一个团队
他教全世界程序员怎么写好代码,答案写在这里!
研发效能提升的实践框架、模式与反模式
聊聊大中型公司都热衷于造轮子的故事
构建健壮的分布式系统
被滥用的“架构师”!